#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# rev-shortlink: Google App Engine application for resolving shortlinks
# Copyright (C) 2009 Sam Johnston, Australian Online Solutions Pty Ltd
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import wsgiref.handlers
import re

from google.appengine.ext import webapp

# pre-create patterns for performance
# rel=shortlink (http://code.google.com/p/shortlink)
pattern_http_shortlink = re.compile(r'\<(.*)\>\; rel\=(shortlink|http\:\/\/purl\.org\/net\/shortlink)?')
pattern_html_shortlink = re.compile(r'(?i)\<head\>.*\<(?:link|a)\s.*?rel\s*\=\s*[\"\'].*?shortlink.*?[\"\'].*?href\s*\=\s*[\"\'](.*?)[\"\'].*?\>.*?\</head\>')
pattern_html_shortlink_reverse = re.compile(r'(?i)\<head\>.*\<(?:link|a)\s.*?href\s*\=\s*[\"\'](.*?)[\"\'].*?rel\s*\=\s*[\"\'].*?shortlink.*?[\"\'].*?\>.*?\</head\>')

# rel=short_url (http://sites.google.com/a/snaplog.com/wiki/short_url)
pattern_http_shorturl  = re.compile(r'\<(.*)\>\; rel\=short[_ ]?ur[li]')
pattern_html_shorturl = re.compile(r'(?i)\<head\>.*\<(?:link|a)\s.*?rel\s*\=\s*[\"\'].*?short[_ ]?ur[li].*?[\"\'].*?href\s*\=\s*[\"\'](.*?)[\"\'].*?\>.*?\</head\>')
pattern_html_shorturl_reverse = re.compile(r'(?i)\<head\>.*\<(?:link|a)\s.*?href\s*\=\s*[\"\'](.*?)[\"\'].*?rel\s*\=\s*[\"\'].*?short[_ ]?ur[li].*?[\"\'].*?\>.*?\</head\>')
pattern_shorturl = re.compile(r'short[_ ]?ur[li]')

# rev=canonical (http://revcanonical.wordpress.com/)
pattern_http_canonical = re.compile(r'\<(.*)\>\; rev\=canonical')
pattern_html_canonical = re.compile(r'(?i)\<head\>.*\<(?:link|a)\s.*?rev\s*\=\s*[\"\'].*?canonical.*?[\"\'].*?href\s*\=\s*[\"\'](.*?)[\"\'].*?\>.*?\</head\>')
pattern_html_canonical_reverse = re.compile(r'(?i)\<head\>.*\<(?:link|a)\s.*?href\s*\=\s*[\"\'](.*?)[\"\'].*?rev\s*\=\s*[\"\'].*?canonical.*?[\"\'].*?\>.*?\</head\>')

class MainHandler(webapp.RequestHandler):

  def get(self):
    notice = ""
    if self.request.get("warning"):
      notice = u"""
<div style="padding: 10px; background-color: #f92; border: solid 1px #e62;"><strong>Warning</strong>: Site returned non-standard short_url (or similar) link: <a href="%s">%s</a></div>
""" % (self.request.get("warning"), self.request.get("warning"))

    if self.request.get("error"):
      notice = u"""
<div style="padding: 10px; background-color: #f88; border: solid 1px #f33; color: 300"><strong>Error</strong>: Site returned dangerous rev=canonical link: <a href="%s">%s</a>. Please contact the site's owner.</div>
""" % (self.request.get("error"), self.request.get("error"))

    response = u"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>rel=shortlink: url shortening that really doesn't hurt the internet</title>
<link rel="shortlink" href="http://purl.org/net/shortlink" />
</head>
<body style="background-color: #eee; margin: 30px; font-family: 'Helvetica Neue','Helvetica','Arial',sans-serif;">
<div style="border: solid 1px #ccc; background-color: #fff; padding: 30px; ">
<h1 style="display: inline;"><span style="color: #339;">rel=</span><span style="color: #33c;">shortlink</span></h1>
<span style="display: inline;">url shortening that really doesn't hurt the internet</span>
""" + notice + u"""
<div>
<form action="/api">
<div style="margin: 10px; padding: 20px; border: solid 1px #ccc;">
URL: <input type="text" name="url" style="width: 400px;" />
Fallback: <select name="fallback">
<option value="" selected="selected">None</option>
<option value="http://bit.ly/api?url=">bit.ly</option>
<option value="http://cli.gs/api/v1/cligs/create?url=">cli.gs</option>
<option value="http://is.gd/api.php?longurl=">is.gd</option>
<option value="http://kl.am/api/shorten/?format=text&amp;url=">kl.am</option>
<option value="http://tinyurl.com/api-create.php?url=">tinyurl.com</option>
<option value="http://tr.im/api/trim_simple?url=">tr.im</option>
</select>
<input type="submit" value="Shorten" />
</div>
</form>
<div style="width:80%; margin-left:10%; margin-right:10%; font-size: small;">
<ul><li><strong>API version</strong>: <code>/api?url=&lt;url&gt;</code>, returning the shortened URL where specified (or every time when optional <code>fallback</code> parameter is provided for legacy sites without <code>shortlink</code>s)
</li>
<li><strong>Bookmarklet</strong>: <a href="javascript:(function(){var url=document.location;var links=document.getElementsByTagName(&#39;link&#39;);var found=0;for(var i = 0, l; l = links[i]; i++){if(l.getAttribute(&#39;rel&#39;)==&#39;shortlink&#39;) {found=l.getAttribute(&#39;href&#39;);break;}}if (!found) {for (var i = 0; l = document.links[i]; i++) {if (l.getAttribute(&#39;rel&#39;) == &#39;shortlink&#39;) {found = l.getAttribute(&#39;href&#39;);break;}}}if (found) {prompt(&#39;URL:&#39;, found);} else {window.onTinyUrlGot = function(r) {if (r.ok) {prompt(&#39;URL:&#39;, r.tinyurl);} else {alert(&#39;Could not shorten with tinyurl&#39;);}};var s = document.createElement(&#39;script&#39;);s.type=&#39;text/javascript&#39;;s.src=&#39;http://json-tinyurl.appspot.com/?callback=onTinyUrlGot&amp;url=&#39; +document.location;document.getElementsByTagName(&#39;head&#39;)[0].appendChild(s);}})();">Shortlink</a> (drag to your browser toolbar, originally by <a rel="nofollow" href="http://simonwillison.net/">Simon Willison</a>) extracts the information from the page or falls back to TinyURL.
</li>
</ul><strong>Who</strong><br />
A community service by <a href="http://samj.net/">Sam Johnston</a> (<a href="http://twitter.com/samj">@samj</a> / <a href="http://mailhide.recaptcha.net/d?k=010ujrCC-dxuqPr117-cOF3g==&amp;c=pukNSs5-PbIW51DwSSw7GQ==" onclick="window.open('http://mailhide.recaptcha.net/d?k=010ujrCC-dxuqPr117-cOF3g==&amp;c=pukNSs5-PbIW51DwSSw7GQ==', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;" title="Reveal this e-mail address">s...@samj.net</a>) of <a href="http://www.aos.net.au/">Australian Online Solutions</a>, loosely based on a relatively good (albeit <a rel="nofollow" href="http://revcanonical.appspot.com/">poorly</a> <a rel="nofollow" href="http://revcanonical.wordpress.com/">executed</a>) idea by some <a rel="nofollow" href="http://shiflett.org/about">some</a> <a rel="nofollow" href="http://laughingmeme.org/#footer">web</a> <a rel="nofollow" href="http://twitter.com/robspychala">developers</a> purporting to "<a rel="nofollow" href="http://shiflett.org/blog/2009/apr/save-the-internet-with-rev-canonical">save the Internet</a>" while actually <a href="http://www.mnot.net/blog/2009/04/14/rev_canonical_bad">hurting it</a>.
<br />
<strong>What</strong><br />
A mechanism for webmasters to indicate the preferred short URL(s) for a given resource, thereby avoiding the need to consult a potentially insecure/unreliable third-party for same. Resulting URLs reveal useful information about the source (domain) and subject (path):
<br /><code><del><a href="http://tinyurl.com/cgy9pu">http://tinyurl.com/cgy9pu</a></del> » <a href="http://purl.org/net/shortlink">http://purl.org/net/shortlink</a></code>
<br />
<strong>Where</strong><br />
The <a href="http://code.google.com/p/shortlink/">shortlink</a> Google Code project, the <a href="http://rel-shortlink.appspot.com/">rel-shortlink</a> Google App Engine application, the <a href="http://search.twitter.com/search?q=%23shortlink">#shortlink</a> Twitter hashtag and coming soon to a client or site near you.
<br />
<strong>When</strong><br />
Starting April 2009, pending <a href="http://www.ietf.org/mail-archive/web/apps-discuss/current/msg00531.html">ongoing discussion</a> in the Internet standards community (in the mean time you can also use <code>http://purl.org/net/shortlink</code> in place of <code>shortlink</code>).
<br />
<strong>Why</strong><br />
Short URLs are useful both for space constrained channels (such as SMS and Twitter) and also for anywhere URLs need to be manually entered (e.g. when they are printed or spoken). Third-party shorteners can cause many problems, including <a href="http://en.wikipedia.org/wiki/Link_rot">link rot</a>, performance problems, outages and privacy &amp; security issues.
<br />
<strong>How</strong><br />
By way of <code style="background-color: yellow;">Link: &lt;url&gt;; rel=shortlink</code> HTTP headers and/or <code style="background-color: yellow;">&lt;link rel="shortlink" href="&lt;url&gt;" /&gt;</code> HTML elements (actually <code style="background-color: yellow;">&lt;a rel="shortlink" href="&lt;url&gt;"&gt;</code> now works too).
</div>
</div>
<div style="text-align: center; font-size: x-small; padding: 15px;">
<p>Another <a href="http://www.aos.net.au/">Australian Online Solutions</a> service Copyright © 2009 by <a href="http://www.linkedin.com/in/samjohnston">Sam Johnston</a><br />
<a href="http://code.google.com/p/shortlink/source/browse/#svn/trunk/appengine/rel-shortlink">Source code</a> available under the <a href="http://www.fsf.org/licensing/licenses/agpl-3.0.html">GNU Affero General Public License</a>.
<!--Copyright © 2009 Australian Online Solutions Pty Ltd-->
<br />
<a href="http://validator.w3.org/check?uri=referer"><img src="http://www.w3.org/Icons/valid-xhtml11-blue" alt="Valid XHTML 1.1" height="31" width="88" style="border-style: none;" /></a>
<a href="http://jigsaw.w3.org/css-validator/check/referer"><img style="border:0;width:88px;height:31px" src="http://jigsaw.w3.org/css-validator/images/vcss-blue" alt="Valid CSS!" /></a>
<br />
<img src="http://code.google.com/appengine/images/appengine-noborder-120x30.gif" alt="Powered by Google App Engine" />
</p></div>
</div>
</body>
</html>
"""
    self.response.out.write(response)

class ApiHandler(webapp.RequestHandler):

  def get(self):
    import urllib
    from google.appengine.api.urlfetch import DownloadError
	
    shortlink = url = self.request.get("url")
    fallback = self.request.get("fallback")

    # basic loop avoidance
    if (fallback.find('rel-shortlink\.appspot\.com') != -1): fallback = None

    # get headers and body
    links = ()
    body = ""
    try:
      f = urllib.urlopen(url)
      links = f.headers.getallmatchingheaders('link')
      body = f.read()
      f.close()
    except DownloadError:
      pass

    # check for HTTP Link: header
    error = warning = None
    for link in links:
      # shortlink detection
      m = pattern_http_shortlink.search(link)
      if (m): shortlink = m.group(1)

      # shorturl warning
      m = pattern_http_shorturl.search(link)
      if (m):
        shortlink = "http://rel-shortlink.appspot.com/?warning=" + m.group(1)
        break

      # canonical error
      m = pattern_http_canonical.search(link)
      if (m):
        shortlink = "http://rel-shortlink.appspot.com/?error=" + m.group(1)
        break

    # check for HTML LINK element
    if (shortlink == url):
      # tried BeautifulSoup - seems to be too fragile since 3.1
#      from BeautifulSoup import BeautifulSoup, MinimalSoup, SoupStrainer

      #Create the soup object from the HTML data
#      soup = MinimalSoup(body, parseOnlyThese=SoupStrainer('link'))

      # find rel=shortlink
#      linkId = soup.find('link', rel='shortlink')
#      if linkId: shortlink = linkId['href']

      # unless there's a rel=short_url
#      linkId = soup.find('link', rel=pattern_shorturl)
#      if linkId: shortlink = 'http://rel-shortlink.appspot.com/?warning=' + linkId['href']

      # or worse, rev=canonical
#      linkId = soup.find('link', rev='canonical')
#      if linkId: shortlink = 'http://rel-shortlink.appspot.com/?error=' + linkId['href']

      # shortlink detection
      m = pattern_html_shortlink.search(body)
      if (m): shortlink = m.group(1)
      # and with reversed attributes
      m = pattern_html_shortlink_reverse.search(body)
      if (m): shortlink = m.group(1)

      # shorturl warning
      m = pattern_html_shorturl.search(body)
      if (m): shortlink = "http://rel-shortlink.appspot.com/?warning=" + m.group(1)
      # and with reversed attributes
      m = pattern_html_shorturl_reverse.search(body)
      if (m): shortlink = "http://rel-shortlink.appspot.com/?warning=" + m.group(1)

      # canonical error
      m = pattern_html_canonical.search(body)
      if (m): shortlink = "http://rel-shortlink.appspot.com/?error=" + m.group(1)
      # and with reversed attributes
      m = pattern_html_canonical_reverse.search(body)
      if (m): shortlink = "http://rel-shortlink.appspot.com/?error=" + m.group(1)

    # return plain text (KISS)
    self.response.headers['Content-Type'] = "text/plain"

    # if url is not shortened then use fallback if specified
    if (shortlink == url) and (fallback):
      #shortlink = urllib.urlopen(fallback + url).read()
      self.redirect(fallback + url)
    else:
      self.response.out.write(shortlink)

def main():
  application = webapp.WSGIApplication([('/', MainHandler),
                                        ('/api', ApiHandler)],
                                       debug=True)
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
  main()
